package cn.iocoder.yudao.module.signature.appconnect.util;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.UUID;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.common.util.ipa.IpaDecompressionUtil;
import cn.iocoder.yudao.module.infra.api.file.FileApi;
import cn.iocoder.yudao.module.signature.appconnect.util.kylinworks.IosPngUtil;
import cn.iocoder.yudao.module.signature.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.signature.model.IpaInfoModel;
import com.dd.plist.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.web.multipart.MultipartFile;
import org.xml.sax.SAXException;

import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * @author liujq
 * @date 20241022 19:02
 * ipa包解析工具
 */
@Slf4j
public class IpaInfoExtractorUtil {


    private IpaInfoExtractorUtil() {
    }

    /**
     * 解压
     * @param args
     * @throws Exception
     */

    public static void main(String[] args) throws Exception {
//        File ipaFile = new File("D:\\Users\\liujq\\wechat\\WeChat Files\\wxid_4qjp6xqz5cpw22\\FileStorage\\File\\2025-01\\微信_8.0.52_多开推送14合1.ipa");
        File ipaFile=new File("D:\\Users\\liujq\\wechat\\WeChat Files\\wxid_4qjp6xqz5cpw22\\FileStorage\\File\\2025-01\\微信_8.0.55多开精简版3.ipa");
//        File ipaFile = new File("D:\\work\\work\\doc\\签名\\全能签.ipa");
//        File ipaFile = new File("D:\\Users\\liujq\\wechat\\WeChat Files\\wxid_4qjp6xqz5cpw22\\FileStorage\\File\\2025-01\\快手_12.11.40_1736335498.ipa");
        File tempDir = Files.createTempDirectory("ipaExtract").toFile();
        IpaDecompressionUtil.ExtractResult extractResult = IpaDecompressionUtil.extractIpa(ipaFile, tempDir.getAbsolutePath());
        if (!extractResult.isSuccess()) {
            log.error("Extract IPA failed: {}", extractResult.getMessage());
            return;
        }
//        unzipIPA(ipaFile, tempDir.getAbsolutePath());
        File appDir = findAppDirectory(tempDir);

        File infoPlistFile = new File(appDir, "Info.plist");
        NSDictionary rootDict = (NSDictionary) PropertyListParser.parse(infoPlistFile);

        // 获取包名、版本、显示名称等信息
        String packageName = rootDict.objectForKey("CFBundleIdentifier").toString();
        String version = rootDict.objectForKey("CFBundleShortVersionString").toString();
        String displayName = rootDict.objectForKey("CFBundleDisplayName").toString();

        // 查找并获取图标文件
        String iconFile = findIconFile(appDir.getAbsolutePath(),  getIconFileName(rootDict));
        String sourceIconName = FileUtil.getName(iconFile);

        IosPngUtil iosPngUtil = new IosPngUtil();
        iconFile = iosPngUtil.convertPNGFile(new File(iconFile));
        Assert.notBlank(iconFile, "Icon file not found.");

    }

    /**
     * 获取ipa包信息
     *
     * @param file ipa包文件
     * @return ipa包信息
     */
    public static IpaInfoModel getIpaInfo(MultipartFile file, FileApi fileApi) throws IOException, PropertyListFormatException, ParseException, ParserConfigurationException, SAXException {
        log.info("Start to extract ipa info.");
        //获取file 后缀 必须是.ipa
        String fileName = file.getOriginalFilename();
        assert fileName != null;
        if (!fileName.endsWith(".ipa")) {
            throw new ServiceException(ErrorCodeConstants.IPA_FILE_SUFFIX_NOT_SATISFIED);
        }

        File tempDir = Files.createTempDirectory("ipaExtract").toFile();

        File ipaFile = File.createTempFile(FileUtil.getPrefix(fileName), FileUtil.getSuffix(fileName));
        file.transferTo(ipaFile);
        log.info("IPA File: {}", ipaFile.getAbsolutePath());
        // 解压ipa到临时目录
        unzipIPA(ipaFile, tempDir.getAbsolutePath());
        log.info("Unzip IPA to: {}", tempDir.getAbsolutePath());
        // 获取解压后的.app目录
        File appDir = findAppDirectory(tempDir);
        if (appDir == null) {
            throw new ServiceException(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR);
        }

        // 解析Info.plist文件
        File infoPlistFile = new File(appDir, "Info.plist");
        NSDictionary rootDict = (NSDictionary) PropertyListParser.parse(infoPlistFile);

        // 获取包名、版本、显示名称等信息
        String packageName = rootDict.objectForKey("CFBundleIdentifier").toString();
        String version = rootDict.objectForKey("CFBundleShortVersionString").toString();
        String displayName = rootDict.objectForKey("CFBundleDisplayName").toString();

        // 查找并获取图标文件
        String iconFile = findIconFile(appDir.getAbsolutePath(),  getIconFileName(rootDict));
        String sourceIconName = FileUtil.getName(iconFile);

        IosPngUtil iosPngUtil = new IosPngUtil();
        iconFile = iosPngUtil.convertPNGFile(new File(iconFile));
        Assert.notBlank(iconFile, "Icon file not found.");



        long fileSize = Files.size(ipaFile.toPath());
        String formattedSize = formatFileSize(fileSize);
        log.info("IPA File Size: {}", formattedSize);

        String ipaName = UUID.fastUUID() + "." + FileUtil.getSuffix(fileName);
        String iconName = UUID.fastUUID() + "." + FileUtil.getSuffix(iconFile);

        //上传安装包和图标文件
        String ipaPath = fileApi.createFile(ipaName ,"up_tmp", FileUtil.readBytes(ipaFile));
        String iconPath = fileApi.createFile(iconName,"up_tmp", FileUtil.readBytes(iconFile));

        // 清理临时目录
        FileUtils.deleteDirectory(tempDir);
        FileUtils.delete(ipaFile);

        return IpaInfoModel.builder()
                .iconImg(iconPath)
                .applicationName(displayName)
                .applicationVersion(version)
                .applicationUrl(ipaPath)
                .bundleId(packageName)
                .applicationSize(formattedSize)
                .iconName(sourceIconName)
                .build();
    }

    /**
     * 解压ipa文件
     *
     * @param ipaFile
     * @param destDir
     * @throws IOException
     */
    public static void unzipIPA(File ipaFile, String destDir) throws IOException {
        try (ZipFile zipFile = new ZipFile(ipaFile)) {
            Enumeration<? extends ZipEntry> entries = zipFile.entries();

            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();
                String entryName = entry.getName();

                // 跳过 Mac 系统的特殊文件
                if (entryName.contains("__MACOSX") || entryName.contains(".DS_Store")) {
                    continue;
                }

                // 统一使用正斜杠
                String fileName = entryName.replace('\\', '/');
                File file = new File(destDir, fileName);

                // 检查路径穿越漏洞
                String canonicalDestDir = new File(destDir).getCanonicalPath();
                String canonicalFile = file.getCanonicalPath();
                if (!canonicalFile.startsWith(canonicalDestDir)) {
                    log.warn("Blocked path traversal attempt: {}", entryName);
                    continue;
                }

                if (entry.isDirectory()) {
                    file.mkdirs();
                } else {
                    // 确保父目录存在
                    File parent = file.getParentFile();
                    if (!parent.exists()) {
                        parent.mkdirs();
                    }

                    try (InputStream is = zipFile.getInputStream(entry);
                         FileOutputStream fos = new FileOutputStream(file)) {
                        byte[] buffer = new byte[8192];
                        int length;
                        while ((length = is.read(buffer)) > 0) {
                            fos.write(buffer, 0, length);
                        }
                    } catch (IOException e) {
                        log.warn("Failed to extract file: {} - Error: {}", fileName, e.getMessage());
                        // 如果是关键文件，则抛出异常
                        if (fileName.endsWith("Info.plist") || fileName.endsWith(".app")) {
                            throw e;
                        }
                        // 非关键文件则继续处理下一个
                        continue;
                    }
                }
            }
        }
    }

    /**
     * 清理文件名，确保跨平台兼容性
     */
    private static String sanitizeFileName(String fileName) {
        // 分割路径和文件名
        int lastSlash = fileName.lastIndexOf('/');
        String path = lastSlash >= 0 ? fileName.substring(0, lastSlash + 1) : "";
        String name = lastSlash >= 0 ? fileName.substring(lastSlash + 1) : fileName;

        // 清理文件名中的特殊字符
        name = name.replaceAll("[\\\\/:*?\"<>|]", "_")  // Windows 非法字符
                .replaceAll("\\s+", "_")               // 空白字符替换为下划线
                .replaceAll("[^\\w.-]", "_");          // 只保留字母、数字、下划线、点和横线

        // 处理以点开始的文件名
        if (name.startsWith(".")) {
            name = "_" + name;
        }

        // 限制文件名长度（Linux 默认限制是255字节）
        if (name.length() > 200) { // 预留一些空间给路径
            String ext = "";
            int dotIndex = name.lastIndexOf('.');
            if (dotIndex > 0) {
                ext = name.substring(dotIndex);
                name = name.substring(0, dotIndex);
            }
            name = name.substring(0, Math.min(name.length(), 200 - ext.length())) + ext;
        }

        // 重新组合路径和文件名
        return path + name;
    }

    /**
     * 检查文件权限并设置
     */
    private static void ensureFilePermissions(File file) {
        // 设置文件权限为 644 (用户可读写，组和其他用户可读)
        file.setReadable(true, false);
        file.setWritable(true, true);
        file.setExecutable(false);

        // 如果是目录，设置为 755 (用户可读写执行，组和其他用户可读执行)
        if (file.isDirectory()) {
            file.setExecutable(true, false);
        }
    }
    /**
     * 查找.app目录
     * @param extractDir
     * @return
     */
    public static File findAppDirectory(File extractDir) {
        File payloadDir = new File(extractDir, "Payload");
        if (payloadDir.exists() && payloadDir.isDirectory()) {
            File[] files = payloadDir.listFiles((dir, name) -> name.endsWith(".app"));
            if (files != null && files.length > 0) {
                return files[0]; // 返回找到的第一个.app目录
            }
        }
        return null;
    }

    /**
     * 获取图标前缀名称
     * @param rootDict
     * @return
     */
    public static String getIconFileName(NSDictionary rootDict) {
        // 1. 尝试获取 CFBundleIcons（iOS 7.0+）
        String iconName = getIconFromCFBundleIcons(rootDict);
        if (iconName != null) {
            return iconName;
        }

        // 2. 尝试获取 CFBundleIconFiles（旧版 iOS）
        iconName = getIconFromCFBundleIconFiles(rootDict);
        if (iconName != null) {
            return iconName;
        }

        // 3. 尝试获取 CFBundleIconFile（macOS）
        iconName = getIconFromCFBundleIconFile(rootDict);
        if (iconName != null) {
            return iconName;
        }

        return null;
    }

    private static String getIconFromCFBundleIcons(NSDictionary rootDict) {
        try {
            NSDictionary iconsDict = (NSDictionary) rootDict.objectForKey("CFBundleIcons");
            if (iconsDict != null) {
                NSDictionary primaryIconsDict = (NSDictionary) iconsDict.objectForKey("CFBundlePrimaryIcon");
                if (primaryIconsDict != null) {
                    NSArray iconFilesArray = (NSArray) primaryIconsDict.objectForKey("CFBundleIconFiles");
                    if (iconFilesArray != null && iconFilesArray.count() > 0) {
                        return iconFilesArray.objectAtIndex(iconFilesArray.count() - 1).toString();
                    }
                }
            }
        } catch (Exception e) {
            // 处理可能的类型转换异常
        }
        return null;
    }

    private static String getIconFromCFBundleIconFiles(NSDictionary rootDict) {
        try {
            NSArray iconFiles = (NSArray) rootDict.objectForKey("CFBundleIconFiles");
            if (iconFiles != null && iconFiles.count() > 0) {
                return iconFiles.objectAtIndex(iconFiles.count() - 1).toString();
            }
        } catch (Exception e) {
            // 处理可能的类型转换异常
        }
        return null;
    }

    private static String getIconFromCFBundleIconFile(NSDictionary rootDict) {
        try {
            NSObject iconFile = rootDict.objectForKey("CFBundleIconFile");
            if (iconFile != null) {
                return iconFile.toString();
            }
        } catch (Exception e) {
            // 处理可能的类型转换异常
        }
        return null;
    }

    /**
     * 获取图标文件
     * @param appDir
     * @param iconFileNamePrefix
     * @return
     */
    public static String findIconFile(String appDir, String iconFileNamePrefix) {
        File appDirectory = new File(appDir);
        if (appDirectory.isDirectory()) {
            File[] iconFiles = appDirectory.listFiles((dir, name) -> name.startsWith(iconFileNamePrefix) && name.endsWith(".png"));
            if (iconFiles != null && iconFiles.length > 0) {
                // 按文件名排序，将高分辨率的放前面
                Arrays.sort(iconFiles, Comparator.comparing(File::getName).reversed());
                return iconFiles[0].getAbsolutePath(); // 返回找到的图标文件路径
            }
        }
        return null;
    }

    /**
     * 获取并格式化应用大小
     * @param sizeBytes
     * @return
     */
    public static String formatFileSize(long sizeBytes) {
        double size = sizeBytes;
        String[] units = {"B", "KB", "MB", "GB", "TB"};
        int unitIndex = 0;

        // 转换为合适的单位
        while (size >= 1024 && unitIndex < units.length - 1) {
            size /= 1024;
            unitIndex++;
        }

        // 使用DecimalFormat保留两位小数
        DecimalFormat df = new DecimalFormat("#.00");
        return df.format(size) + " " + units[unitIndex];
    }
}
